//=========================================================================
// Copyright (C) 2024 The C++ Component Model(COMO) Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=========================================================================

/*  =========================================================================
    zmtp_dealer - DEALER socket class

    Copyright (c) contributors as noted in the AUTHORS file.
    This file is part of libzmtp, the C ZMTP stack.

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at http://mozilla.org/MPL/2.0/.
    =========================================================================
*/

#include "zmtp_classes.h"

// Structure of our class
struct _zmtp_dealer_t {
    zmtp_channel_t *channel;    // At most one channel per socket now
};

CMemPool *g_zmtp_dealer_buffer = NULL;

/**
 * Constructor
 */
zmtp_dealer_t *zmtp_dealer_new()
{
    zmtp_dealer_t *self = NULL;
    if (NULL != g_zmtp_dealer_buffer) {
        self = (zmtp_dealer_t *)CMemPool_Alloc(g_zmtp_dealer_buffer,
                                      sizeof(zmtp_dealer_t), MUST_USE_MEM_POOL);
    }
    if (NULL == self) {
        self = (zmtp_dealer_t *)zmalloc(sizeof(zmtp_dealer_t));
    }

    if (NULL == self) {
        return NULL;
    }

    self->channel = NULL;
    return self;
}

/**
 * Destructor
 */
void zmtp_dealer_destroy(zmtp_dealer_t **self_p)
{
    if (NULL == self_p) {
        return;
    }

    if (NULL != *self_p) {
        zmtp_dealer_t *self = *self_p;
        zmtp_channel_destroy(&self->channel);

        if ((NULL != g_zmtp_dealer_buffer) &&
                            (CMemPool_CheckExist(g_zmtp_dealer_buffer, self))) {
            CMemPool_Free(g_zmtp_dealer_buffer, self);
        }
        else {
            zfree(self);
        }

        *self_p = NULL;
    }
}

/**
 * zmtp_dealer_ipc_connect
 */
int zmtp_dealer_ipc_connect(zmtp_dealer_t *self, const char *path)
{
    // At most one channel per socket now
    if ((NULL == self) || (NULL != self->channel)) {
        return -1;
    }

    // Create new channel if possible
    self->channel = zmtp_channel_new();
    if (NULL == self->channel) {
        return -2;
    }

    // Try to connect channel to specified endpoint
    if (zmtp_channel_ipc_connect(self->channel, path) == -1) {
        zmtp_channel_destroy(&self->channel);
        return -3;
    }
    return 0;
}

/**
 * zmtp_dealer_tcp_connect
 */
int zmtp_dealer_tcp_connect(zmtp_dealer_t *self, const char *addr, unsigned short port)
{
    // At most one channel per socket now
    if ((NULL == self) || (NULL != self->channel)) {
        return -1;
    }

    // Create new channel if possible
    self->channel = zmtp_channel_new();
    if (NULL == self->channel) {
        return -3;
    }
    
    // Try to connect channel to specified endpoint
    if (zmtp_channel_tcp_connect(self->channel, addr, port) == -1) {
        zmtp_channel_destroy(&self->channel);
        return -4;
    }
    return 0;
}

/**
 * zmtp_dealer_connect
 */
int zmtp_dealer_connect(zmtp_dealer_t *self, const char *endpoint_str)
{
    if ((NULL == self) || (NULL != self->channel)) {
        return -1;
    }

    // Create new channel if possible
    self->channel = zmtp_channel_new();
    if (NULL == self->channel) {
        return -2;
    }

    // Try to connect channel to specified endpoint
    int iRet = zmtp_channel_connect(self->channel, endpoint_str);
    if (iRet < 0) {
        zmtp_channel_destroy(&self->channel);
        return -3 + iRet;
    }
    return 0;
}

/**
 * zmtp_dealer_listen
 *
 * Use fd to associate existing socket, fd must be in the wait listen state.
 */
int zmtp_dealer_listen(zmtp_dealer_t *self, const char *endpoint_str, void *channel)
{
    if ((NULL == self) || (NULL != self->channel)) {
        return -1;
    }

    // Create new channel if possible
    self->channel = zmtp_channel_new();
    if (NULL == self->channel) {
        return -2;
    }

    // Try to connect channel to specified endpoint
    int iRet = zmtp_channel_listen(self->channel, endpoint_str, channel);
    if (iRet < 0) {
        zmtp_channel_destroy(&self->channel);
        return -3 + iRet;
    }
    return 0;
}

/**
 * Send a message on a socket
 */
int zmtp_dealer_send(zmtp_dealer_t *self, zmtp_msg_t *msg)
{
    if ((NULL == self) || (NULL == self->channel)) {
        return -1;
    }

    return zmtp_channel_send(self->channel, msg);
}

/**
 * zmtp_dealer_recv
 */
zmtp_msg_t *zmtp_dealer_recv(zmtp_dealer_t *self)
{
    if ((NULL == self) || (NULL == self->channel)) {
        return NULL;
    }

    return zmtp_channel_recv(self->channel);
}

/**
 * zmtp_dealer_get_socket
 *
 * -------------------------------- program by select module
 * fd_set readfds;
 * struct timeval tv;
 * int retval;
 *
 * FD_ZERO(&readfds);
 * FD_SET(fd, &readfds);
 *
 * tv.tv_sec = 1;  // 1 sec timeout
 * tv.tv_usec = 0;
 *
 * retval = select(maxfd + 1, &readfds, NULL, NULL, &tv);
 */
int zmtp_dealer_get_socket(zmtp_dealer_t *self)
{
    if ((NULL == self) || (NULL == self->channel)) {
        return -1;
    }

    return self->channel->fd;
}

#ifdef Use_CMemPoolSet
CMemPoolSet *cmemPoolSet;

extern CMemPool *g_zmtp_channel_buffer;
extern CMemPool *g_zmtp_ipc_endpoint_buffer;
extern CMemPool *g_zmtp_tcp_endpoint_buffer;
extern CMemPool *g_zmtp_msg_buffer;

void *zmtp_CMemPoolSet()
{
    g_zmtp_channel_buffer = CMemPool_New2(NULL, 10, sizeof(zmtp_channel_t));
    g_zmtp_dealer_buffer = CMemPool_New2(NULL, 10, sizeof(zmtp_channel_t));
    g_zmtp_ipc_endpoint_buffer = CMemPool_New2(NULL, 10, sizeof(zmtp_ipc_endpoint_t));
    g_zmtp_tcp_endpoint_buffer = CMemPool_New2(NULL, 10, sizeof(zmtp_tcp_endpoint_t));
    g_zmtp_msg_buffer = CMemPool_New2(NULL, 10, sizeof(zmtp_msg_t));

    MemPoolItem items[] = { {10, 64, NULL},
                            {10, 128, NULL},
                            {10, 256, NULL},
                            {10, 512, NULL},
                            {10, 1024, NULL},
                            {10, 2048, NULL},
                            {10, MAX_ZMTP_MSG_SIZE, NULL},
                          };
    cmemPoolSet = CMemPoolSet_New(items, sizeof(items)/sizeof(MemPoolItem));

    return cmemPoolSet;
}
#endif

#ifdef __SELF_TEST__

// --------------------------------------------------------------------------
// Selftest

void zmtp_dealer_test(bool verbose)
{
    printf(" * zmtp_dealer: ");
    // @selftest
    zmtp_dealer_t *dealer = zmtp_dealer_new();

    int iRet = zmtp_dealer_listen(dealer, (const char *)"tcp://127.0.0.1:4800", -1);
    if (iRet != 0) {
        printf("fail, zmtp_dealer_listen: %d\n", iRet);
        return;
    }
    
    printf("Now recving...(zmtp_dealer_recv)\n");
    zmtp_msg_t *msg = zmtp_dealer_recv(dealer);
    byte *bytes = zmtp_msg_data(msg);
    printf("%p\n", (char*)bytes);

    printf("Now sending...(zmtp_dealer_send)\n");
    zmtp_dealer_send(dealer, msg);
    // @end
    printf("OK\n");
}

#endif
